题目描述:
运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制 。
实现 LRUCache
类:
LRUCache(int capacity)
以正整数作为容量capacity
初始化 LRU 缓存int get(int key)
如果关键字key
存在于缓存中,则返回关键字的值,否则返回-1
。void put(int key, int value)
如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字-值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。
进阶:你是否可以在 O(1)
时间复杂度内完成这两种操作?
示例:
1 | 输入 |
提示:
- $1 <= capacity <= 3000$
- $0 <= key <= 3000$
- $0 <= value <= 10^4$
- 最多调用 $3 * 10^4$ 次
get
和put
链接:
https://leetcode-cn.com/problems/lru-cache
题目分析
LRU 也即 Least Recently Used,最近最少使用。顾名思义,最近最少使用的结点会从缓存删去,那么怎么判断哪个结点是最近没有被使用到的呢?我们可以维护一个双向链表作为缓存,越靠近头部表示上一次被使用的时间越近,如果一个结点被使用,就将其移动到链表头部(没有则也是添加到头部),而如果缓存区已满,则删除链表末尾的结点。而我们怎么快速找到结点呢?题目说明了每个结点具有不同的关键字,则我们可以使用一个哈希表来存储结点的指针。所以我们采用的是一个双向链表加一个哈希表的数据结构。(PS:为什么要使用双向链表而不是单向链表呢?因为我们需要频繁地从链表中删除结点(包括移动操作和删除操作),使用双向链表才可以在定位后对前后进行连接操作。)
从实现的细节上来说,我们经常操作的是链表的头结点和尾结点,为了避免多次判定链表是否为空(链表为空时添加的新结点既是头结点也是尾结点),我们使用了两个哨兵结点,分别作为伪头部和伪尾部。
各操作的作用已经包含到代码的注释中。
1 | class LRUCache { |
时间复杂度:$O(1)$。哈希表的查询是 $O(1)$ 的,各项操作也都是 $O(1)$ 的。
空间复杂度:$O(capacity)$。我们需要建立哈希表和双向链表,他们的最大大小都是 $O(capacity)$ 的。